package liquibase.changelog;

import liquibase.ContextExpression;
import liquibase.Contexts;
import liquibase.LabelExpression;
import liquibase.Labels;
import liquibase.configuration.LiquibaseConfiguration;
import liquibase.database.Database;
import liquibase.database.DatabaseList;
import liquibase.exception.DatabaseException;
import liquibase.parser.ChangeLogParserCofiguration;
import liquibase.util.StringUtil;

import java.math.BigInteger;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @ClassName ChangeLogParameters
 * @Author Mr.Han
 * @Description //TODO $
 * @create 2024/7/28 11:39
 **/
public class ChangeLogParameters {
    private List<ChangeLogParameter> changeLogParameters;
    private ChangeLogParameters.ExpressionExpander expressionExpander;
    private Database currentDatabase;
    private Contexts currentContexts;
    private LabelExpression currentLabelExpression;

    public ChangeLogParameters() {
        this((Database)null);
    }

    public ChangeLogParameters(Database database) {
        this.changeLogParameters = new ArrayList();
        LinkedHashMap<Object, Object> externalParameters = new LinkedHashMap();
        externalParameters.putAll(System.getenv());
        externalParameters.putAll((Properties)System.getProperties().clone());
        Iterator var3 = externalParameters.entrySet().iterator();

        while(var3.hasNext()) {
            Map.Entry entry = (Map.Entry)var3.next();
            this.changeLogParameters.add(new ChangeLogParameters.ChangeLogParameter(entry.getKey().toString(), entry.getValue()));
        }

        if (database != null) {
            this.set("database.autoIncrementClause", database.getAutoIncrementClause((BigInteger)null, (BigInteger)null, (String)null, (Boolean)null));
            this.set("database.currentDateTimeFunction", database.getCurrentDateTimeFunction());
            this.set("database.databaseChangeLogLockTableName", database.getDatabaseChangeLogLockTableName());
            this.set("database.databaseChangeLogTableName", database.getDatabaseChangeLogTableName());

            try {
                this.set("database.databaseMajorVersion", database.getDatabaseMajorVersion());
            } catch (DatabaseException var8) {
            }

            try {
                this.set("database.databaseMinorVersion", database.getDatabaseMinorVersion());
            } catch (DatabaseException var7) {
            }

            this.set("database.databaseProductName", database.getDatabaseProductName());

            try {
                this.set("database.databaseProductVersion", database.getDatabaseProductVersion());
            } catch (DatabaseException var6) {
            }

            this.set("database.defaultCatalogName", database.getDefaultCatalogName());
            this.set("database.defaultSchemaName", database.getDefaultSchemaName());
            this.set("database.defaultSchemaNamePrefix", StringUtil.trimToNull(database.getDefaultSchemaName()) == null ? "" : "." + database.getDefaultSchemaName());
            this.set("database.lineComment", database.getLineComment());
            this.set("database.liquibaseSchemaName", database.getLiquibaseSchemaName());
            this.set("database.typeName", database.getShortName());
            try {
                //  this.set("database.databaseMinorVersion", database.getDatabaseMinorVersion());
                this.set("database.databaseMinorVersion", "2");
            } catch (Exception ignore) {
            }
            try {
                this.set("database.isSafeToRunUpdate", database.isSafeToRunUpdate());
            } catch (DatabaseException var5) {
            }

            this.set("database.requiresPassword", database.requiresPassword());
            this.set("database.requiresUsername", database.requiresUsername());
            this.set("database.supportsForeignKeyDisable", database.supportsForeignKeyDisable());
            this.set("database.supportsInitiallyDeferrableColumns", database.supportsInitiallyDeferrableColumns());
            this.set("database.supportsRestrictForeignKeys", database.supportsRestrictForeignKeys());
            this.set("database.supportsSchemas", database.supportsSchemas());
            this.set("database.supportsSequences", database.supportsSequences());
            this.set("database.supportsTablespaces", database.supportsTablespaces());
            this.set("database.supportsNotNullConstraintNames", database.supportsNotNullConstraintNames());
        }

        this.expressionExpander = new ChangeLogParameters.ExpressionExpander(this);
        this.currentDatabase = database;
        this.currentContexts = new Contexts();
        this.currentLabelExpression = new LabelExpression();
    }

    public void setContexts(Contexts contexts) {
        this.currentContexts = contexts;
    }

    public Contexts getContexts() {
        return this.currentContexts;
    }

    public List<ChangeLogParameters.ChangeLogParameter> getChangeLogParameters() {
        return Collections.unmodifiableList(this.changeLogParameters);
    }

    public void set(String paramter, Object value) {
        ChangeLogParameters.ChangeLogParameter param = this.findParameter(paramter, (DatabaseChangeLog)null);
        if (param == null) {
            this.changeLogParameters.add(new ChangeLogParameters.ChangeLogParameter(paramter, value));
        }

    }

    public void set(String key, String value, String contexts, String labels, String databases, boolean globalParam, DatabaseChangeLog changeLog) {
        this.set(key, value, new ContextExpression(contexts), new Labels(labels), databases, globalParam, changeLog);
    }

    public void set(String key, String value, ContextExpression contexts, Labels labels, String databases, boolean globalParam, DatabaseChangeLog changeLog) {
        if (globalParam) {
            ChangeLogParameters.ChangeLogParameter param = this.findParameter(key, (DatabaseChangeLog)null);
            if (param == null) {
                this.changeLogParameters.add(new ChangeLogParameters.ChangeLogParameter(key, value, contexts, labels, databases, globalParam, changeLog));
            }
        } else {
            this.changeLogParameters.add(new ChangeLogParameters.ChangeLogParameter(key, value, contexts, labels, databases, globalParam, changeLog));
        }

    }

    public Object getValue(String key, DatabaseChangeLog changeLog) {
        ChangeLogParameters.ChangeLogParameter parameter = this.findParameter(key, changeLog);
        return parameter != null ? parameter.getValue() : null;
    }

    private ChangeLogParameters.ChangeLogParameter findParameter(String key, DatabaseChangeLog changeLog) {
        ChangeLogParameters.ChangeLogParameter result = null;
        List<ChangeLogParameters.ChangeLogParameter> found = new ArrayList();
        Iterator var5 = this.changeLogParameters.iterator();

        ChangeLogParameters.ChangeLogParameter changeLogParameter;
        while(var5.hasNext()) {
            changeLogParameter = (ChangeLogParameters.ChangeLogParameter)var5.next();
            if (changeLogParameter.getKey().equalsIgnoreCase(key) && changeLogParameter.isValid()) {
                found.add(changeLogParameter);
            }
        }

        if (found.size() > 0) {
            var5 = found.iterator();

            while(var5.hasNext()) {
                changeLogParameter = (ChangeLogParameters.ChangeLogParameter)var5.next();
                if (changeLogParameter.isGlobal()) {
                    result = changeLogParameter;
                    break;
                }
            }

            if (result == null && changeLog != null) {
                DatabaseChangeLog changeLogOrParent = changeLog;

                do {
                    Iterator var9 = found.iterator();

                    while(var9.hasNext()) {
                        ChangeLogParameters.ChangeLogParameter changeLogParameter1 = (ChangeLogParameters.ChangeLogParameter)var9.next();
                        if (changeLogParameter1.getChangeLog().equals(changeLogOrParent)) {
                            result = changeLogParameter1;
                            break;
                        }
                    }
                } while(result == null && (changeLogOrParent = changeLogOrParent.getParentChangeLog()) != null);
            }
        }

        return result;
    }

    public boolean hasValue(String key, DatabaseChangeLog changeLog) {
        return this.findParameter(key, changeLog) != null;
    }

    public String expandExpressions(String string, DatabaseChangeLog changeLog) {
        return this.expressionExpander.expandExpressions(string, changeLog);
    }

    public void setLabels(LabelExpression labels) {
        this.currentLabelExpression = labels;
    }

    public LabelExpression getLabels() {
        return this.currentLabelExpression;
    }

    public class ChangeLogParameter {
        private String key;
        private Object value;
        private ContextExpression validContexts;
        private Labels labels;
        private List<String> validDatabases;
        private boolean global;
        private DatabaseChangeLog changeLog;

        public ChangeLogParameter(String key, Object value) {
            this.global = true;
            this.key = key;
            this.value = value;
        }

        public ChangeLogParameter(String key, Object value, String validContexts, String labels, String validDatabases, boolean globalParam, DatabaseChangeLog changeLog) {
            this(key, value, (ContextExpression)(new ContextExpression(validContexts)), (Labels)(new Labels(labels)), (List)StringUtil.splitAndTrim(validDatabases, ","), globalParam, changeLog);
        }

        private ChangeLogParameter(String key, Object value, ContextExpression validContexts, Labels labels, String validDatabases, boolean globalParam, DatabaseChangeLog changeLog) {
            this(key, value, (ContextExpression)validContexts, (Labels)labels, (List)StringUtil.splitAndTrim(validDatabases, ","), globalParam, changeLog);
        }

        public ChangeLogParameter(String key, Object value, ContextExpression validContexts, Labels labels, List<String> validDatabases, boolean globalParam, DatabaseChangeLog changeLog) {
            this.global = true;
            this.key = key;
            this.value = value;
            this.validContexts = validContexts;
            this.labels = labels;
            this.validDatabases = validDatabases;
            this.global = globalParam;
            this.changeLog = changeLog;
        }

        public String getKey() {
            return this.key;
        }

        public Object getValue() {
            return this.value;
        }

        public List<String> getValidDatabases() {
            return this.validDatabases;
        }

        public ContextExpression getValidContexts() {
            return this.validContexts;
        }

        public Labels getLabels() {
            return this.labels;
        }

        public String toString() {
            return this.getValue().toString();
        }

        public boolean isValid() {
            boolean isValid = this.validContexts == null || this.validContexts.matches(ChangeLogParameters.this.currentContexts);
            if (isValid) {
                isValid = this.labels == null || ChangeLogParameters.this.currentLabelExpression == null || ChangeLogParameters.this.currentLabelExpression.matches(this.labels);
            }

            if (isValid) {
                isValid = DatabaseList.definitionMatches(this.validDatabases, ChangeLogParameters.this.currentDatabase, true);
            }

            return isValid;
        }

        public boolean isGlobal() {
            return this.global;
        }

        public DatabaseChangeLog getChangeLog() {
            return this.changeLog;
        }
    }

    protected static class ExpressionExpander {
        private boolean enableEscaping;
        private ChangeLogParameters changeLogParameters;
        private static final Pattern EXPRESSION_PATTERN = Pattern.compile("(\\$\\{[^\\}]+\\})");

        public ExpressionExpander(ChangeLogParameters changeLogParameters) {
            this.changeLogParameters = changeLogParameters;
            this.enableEscaping = ((ChangeLogParserCofiguration) LiquibaseConfiguration.getInstance().getConfiguration(ChangeLogParserCofiguration.class)).getSupportPropertyEscaping();
        }

        public String expandExpressions(String text, DatabaseChangeLog changeLog) {
            if (text == null) {
                return null;
            } else {
                Matcher matcher = EXPRESSION_PATTERN.matcher(text);
                String originalText = text;

                while(matcher.find()) {
                    String expressionString = originalText.substring(matcher.start(), matcher.end());
                    String valueTolookup = expressionString.replaceFirst("\\$\\{", "").replaceFirst("\\}$", "");
                    Object value = this.enableEscaping && valueTolookup.startsWith(":") ? null : this.changeLogParameters.getValue(valueTolookup, changeLog);
                    if (value != null) {
                        text = text.replace(expressionString, value.toString());
                    }
                }

                if (this.enableEscaping) {
                    text = text.replaceAll("\\$\\{:(.+?)}", "\\$\\{$1}");
                }

                return text;
            }
        }
    }
}

